#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G06 -- S09 ------------------------------------------------------------##
__all__ = []

from .. import comparecards as cmp_cards
from .. import formlib as layout
from .. import helpstrings as hs
from .. import pilelib as stack
from ..gamelib import info as gi, game
from ..hint import CautiousDefaultHint
from ..packlib import info as cs, place

GT, SL = gi.GT, gi.SL
Wrap = stack.StackWrapper


class StHelena_Talon(stack.DealRowRedealTalon):
    """
    Deal and redeal _row_ all cards in reverse order.
    """

    def deal_cards(self, **kw):
        """
        deal_cards([sound:bool=False, shuffle=False, reverse=True]) -> int

        Actual dealing, called from game.deal_cards(). Overide default, to
        reverse _piles when dealing.

        :keyword boolean sound: sound fx?
        :keyword boolean shuffle: shuffle cards before redealing?
        :keyword boolean reverse: deals cards in reverse order?
        :return: number of cards dealt.
        :raises: AssertionError when cards remain after deal
        """
        kw.setdefault('reverse', True)
        num_cards = super(StHelena_Talon, self).deal_cards(**kw)
        assert not self.cards
        return num_cards


class LesQuatreCoins_Talon(stack.DealRowRedealTalon):
    """
    Deal and redeal _row_ all cards in unique order. [1, 5, 6, 7, 8, 3, 9, 10,
    11, 12, 4]
    """

    @property
    def _piles(self):
        """
        _piles -> tup

        :return: deal Game.s.rows
        :rtype: tuple
        """
        r = self.game.s['rows']
        return r[:1] + r[4:8] + r[2:3] + r[1:2] + r[8:] + r[3:4]


class StHelena_Foundation(stack.SS_Foundation):
    """
    Build in rank by same suit. 1st round: base KING pile cards from pile
    [7..10] of _row_ and base ACE pile cards from pile [1..4] of _row_.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Check parent.cardsBuild() and plays from _row_ either side depending
        on base_rank.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        if not super(StHelena_Foundation, self).cardsBuild(from_stack, cards):
            return False
        s, base_rank = self.game.s, self.cap['base_rank']
        if s['talon'].round == 1:
            if base_rank in (cs.KING, cs.ACE):
                i = 6 if base_rank == cs.KING else 0
                if from_stack in s['rows'][i:i + 4]:
                    return False
        return True


class LesQuatreCoins_Foundation(stack.SS_Foundation):
    """
    Build in rank by same suit. Pile [1..4] of _row_ finish regardless. Other
    pile of _row_ finish while _talon_ has cards only from neighbor.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Check parent.cardsBuild() and plays when from _row_ neighbor while
        _talon_ has cards or any outer cards.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        if not super(LesQuatreCoins_Foundation, self).cardsBuild(from_stack,
                                                                 cards):
            return False
        s = self.game.s
        if self.cards and s['talon'].cards and from_stack in s.rows[4:]:
            return s['foundations'].index(self) == s['rows'].index(from_stack) - 4
        return True


class UD_RK_Foundation(stack.RK_Foundation):
    """
    Build up in rank alone.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        :param sequence cards: cards from sending pile
        :return: accept card sequence?
        :rtype: boolean
        """
        return cmp_cards.isEitherRankSequence(cards, **self.cap)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.EITHER_RK_STR

    def closeStack(self):
        pass


class Striptease_RowStack(stack.UD_RK_RowStack):
    """
    Build either in rank alone. (eg. J K J). _BOTTOM_IMAGE_ is shade.
    """
    _BOTTOM_IMAGE_ = 'shade'

    def _build_sequence(self, cards):
        """
        _build_sequence(cards:tup) -> bool

        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean
        """
        cardset = self.TOPCARD['rank'], cards[0]['rank']
        if cardset == (cs.JACK, cs.KING) or cardset == (cs.KING, cs.JACK):
            return True
        return super(Striptease_RowStack, self)._sequence(cards)


class ET_UD_RK_RowStack(stack.EmptyTalonBuildMethod, stack.UD_RK_RowStack):
    pass


class ET_UD_SS_RowStack(stack.EmptyTalonBuildMethod, stack.UD_SS_RowStack):
    pass


class Striptease_Reserve(stack.OpenStack):
    """
    Build none. Play top. Flip only 2nd card from bottom.
    """

    def cardFlips(self):
        """
        cardFlips() -> bool

        :return: flip |top|?
        :rtype: boolean
        """
        if not super(Striptease_Reserve, self).cardFlips():
            return False
        return all(len(r.cards) == 2 for r in self.game.s['reserves'])


class StHelena(game.Game):
    """
    Finish cards on _foundation_ within 3 turn.

    _row_
      Pile 12 around _foundation_. Init deal 8 card. Build either in rank
      alone. Play top. Base none. 1st round N S finish neighbor _foundation_.

     _talon_
      Pile 1 NW. Reset _row_.

    _foundation_
      Pile 8 horz center. Init deal base KING ACE. Build in rank by same suit.
      N down. S up. Play top.

    seealso::
      <http://en.wikipedia.org/wiki/St._Helena_%28solitaire%29>
    ------
    :cvar Stack _talon_: StHelena_Talon (max_rounds=3)
    :cvar Stack _foundations_: StHelena_Foundation (base_rank=KING,
      dir=-1), StHelena_Foundation
    :cvar Stack _row_: stack.UD_RK_RowStack (base_rank=GAP)
    :cvar HINT _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=302, name='St. Helena', game_type=GT.TWO_DECK, decks=2,
                    redeals=2, skill_level=SL.BALANCED, version='0.80fc',
                    altnames=('Napoleonʼs Favorite',
                              'Washingtonʼs Favorite',))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = Wrap(StHelena_Talon, max_rounds=3)
    _foundations_ = (StHelena_Foundation,
                     Wrap(StHelena_Foundation, base_rank=cs.KING, dir=-1),)
    _row_ = Wrap(stack.UD_RK_RowStack, base_rank=cs.GAP)
    _hint_ = CautiousDefaultHint

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes.

        .. \_create_game() -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (3 * l.XM + 6 * l.XS, 3 * l.YM + 4 * l.YS,)
        for xm, xs, ym, ys in ((2, 1, 1, 0), (2, 2, 1, 0), (2, 3, 1, 0),
                               (2, 4, 1, 0), (3, 5, 2, 1), (3, 5, 2, 2),
                               (2, 4, 3, 3), (2, 3, 3, 3), (2, 2, 3, 3),
                               (2, 1, 3, 3), (1, 0, 2, 2), (1, 0, 2, 1),):
            x, y = xm * l.XM + xs * l.XS, ym * l.YM + ys * l.YS  # account +mgns
            s['rows'].append(self._row_(x, y, game=self, offset=(0, 0)))
        dx, dy = l.XM + l.XS, l.YM + l.YS
        for y, _foundation_ in zip(l.col_iter(y=2 * dy, npiles=2, dy=-2 * l.XS),
                                   self._foundations_):
            for x, suit in zip(l.row_iter(x=2 * dx, npiles=l.NSUIT), l.SUIT):
                s['foundations'].append(_foundation_(x, y, game=self,
                                                     suit=suit))
        s['talon'] = self._talon_(l.XM, l.YM, game=self)
        l.defaultAll()
        return l

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return ACE and KING of suit on bottom by rank, suit, deck for
        _foundation_ deal.
        """
        return place.MoveToBottom(cards, place.OneAceKing)

    def _start(self, rows=8, flip=True, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: *rows* ea *flip* all open.
          - s[foundation] 1 ea.

        .. \_start([flip=True, rows=8]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0, flip=flip)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].dealRow(self.s['foundations'])


class BoxKite(StHelena):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 12 around _foundation_. Init deal 8 card. Build either in rank
      alone wrap. Play top. Base none.

    _foundation_
      Pile 8 horz center. Init deal base KING ACE. Build in rank by same suit.
      N down. S up. Play top.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundations_: stack.SS_Foundation,
      stack.SS_Foundation (base_rank=KING, dir=-1)
    :cvar Stack _row_: stack.UD_RK_RowStack (base_rank=GAP, mod=13)
    :cvar HINT _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=408, name='Box Kite', game_type=GT.TWO_DECK, decks=2,
                    redeals=0, skill_level=SL.BALANCED, version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundations_ = (stack.SS_Foundation,
                     Wrap(stack.SS_Foundation, base_rank=cs.KING,
                                  dir=-1),)
    _row_ = Wrap(stack.UD_RK_RowStack, base_rank=cs.GAP, mod=13)


class LesQuatreCoins(game.Game):
    """
    Finish cards on _foundation_ within 3 turn.

    _row_
      Pile 12 over 2 col E W. Init deal a card. Build either in rank alone.
      Play top. Base none. Finish on neighbor _foundation_ while
      _talon_ has cards. Outer cards finish regardless.

     _talon_
      Pile 1 NW. Deal 1 card on _row_.

    _foundation_
      Pile 8 over 2 col. Build in rank by same suit. W down. E up. Play top.
    ------
    :cvar Stack _talon_: LesQuatreCoins_Talon (max_rounds=3)
    :cvar Stack _foundations_: LesQuatreCoins_Foundation,
      LesQuatreCoins_Foundation (base_rank=KING, dir=-1)
    :cvar Stack _row_: ET_UD_RK__RowStack (base_rank=GAP)
    :cvar HINT _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=620, name='Les Quatre Coins', game_type=GT.TWO_DECK,
                    decks=2, redeals=2, skill_level=SL.MOSTLY_SKILL,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = Wrap(LesQuatreCoins_Talon, max_rounds=3)
    _foundations_ = (LesQuatreCoins_Foundation,
       Wrap(LesQuatreCoins_Foundation, base_rank=cs.KING, dir=-1),)
    _row_ = Wrap(ET_UD_RK_RowStack, base_rank=cs.GAP)
    _hint_ = CautiousDefaultHint

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes.

        .. \_create_game() -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 7 * l.XS, l.YM + (l.NSUIT + 1) * l.YS,)
        dx, dy = l.XM + l.XS, l.YM
        for y in l.row_iter(y=dy, npiles=2, dy=l.NSUIT * l.YS):
            for x in l.col_iter(x=dx, npiles=2, dx=4 * l.XS):
                s['rows'].append(self._row_(x, y, game=self, offset=(0, 0)))
        dx += l.XS
        dy += l.HYS
        for x in l.col_iter(x=dx, npiles=2, dx=3 * l.XS):
            for y in l.row_iter(y=dy, npiles=l.NSUIT):
                s['rows'].append(self._row_(x, y, game=self, offset=(0, 0)))
        dx += l.XS
        for x, suit in zip(l.row_block_iter(x=dx, y=dy, npiles=l.NSUITS,
                                            nrows=l.NSUIT),
                           l.SUITS):
            s['foundations'].append(_foundation_(x, y, game=self, suit=suit))
        s['talon'] = LesQuatreCoins_Talon(l.XM, int(self.height / 2), game=self)
        l.set_ncards_text(s['talon'], 's')
        l.set_rounds_text(s['talon'], 'nn')
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: 1 ea open.

        .. \_start([]) -> None
        """
        self._play_sound('deal')
        self.s['talon'].deal_cards()


class RegalFamily(game.Game):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 20 around _foundation_. Init deal 1 card. Build either in rank by
      same suit. Base none.

    _talon_
      A pile W. Deal 1 card on _row_.

    _foundation_
      Pile 9 over 3 row center. Build in rank by same suit wrap. Center in
      diamonds W up from base ACE and E down from JACK. Others down from base
      of TEN. Play top.
    ------
    :cvar Stack _talon_: stack.DealRowTalon
    :cvar Stack _foundations_: stack.SS_Foundation (base_rank=9, mod=13,
      dir=-1) * 2, stack.SS_Foundation (suit=3, base_rank=ACE, mod=13),
      stack.SS_Foundation (suit=3, base_rank=JACK, mod=13, dir=-1)
    :cvar Stack _row_: RegalFamily_RowStack (base_rank=GAP)
    :cvar HINT _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=621, name='Regal Family', game_type=GT.TWO_DECK,
                    decks=2, redeals=0, skill_level=SL.BALANCED,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.DealRowTalon
    _foundations_ = (Wrap(stack.SS_Foundation, base_rank=cs.TEN, mod=13,
                          dir=-1),
                    Wrap(stack.SS_Foundation, suit=3, base_rank=cs.ACE, mod=13),
                    Wrap(stack.SS_Foundation, suit=3, base_rank=cs.JACK, mod=13,
                          dir=-1),)
    _row_ = Wrap(ET_UD_SS_RowStack, base_rank=cs.GAP)
    _hint_ = CautiousDefaultHint

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes.

        .. \_create_game() -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 8 * l.XS, l.YM + 5 * l.YS,)
        for dx, dy in ((0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0),
                       (6, 1), (6, 2), (6, 3), (6, 4), (5, 4), (4, 4), (3, 4),
                       (2, 4), (1, 4), (0, 4), (0, 3), (0, 2), (0, 1),):
            x, y = l.delta_coords(x=l.XM + l.XS, dx=dx, dy=dy)
            s['rows'].append(self._row_(x, y, game=self, offset=(0, 0)))
        dx, dy = l.XM + 3 * l.XS, l.YM + l.YS
        _foundation_ = self._foundations_[0]
        for y in l.col_iter(y=dy, npiles=2, dy=2 * l.YS):
            for x, suit in zip(l.row_iter(x=dx, npiles=l.NSUIT -1),
                               l.SUIT[:-1]):
                s['foundations'].append(_foundation_(x, y, game=self,
                                                     suit=suit))
        y = dy + l.YS
        for x, _foundation_ in zip(l.row_iter(x=dx, npiles=2, dx=2 * l.XS),
                                   self._foundations_[2:]):
            s['foundations'].append(_foundation_(x, y, game=self))
        s['talon'] = self._talon_(l.XM, y, game=self)
        l.set_ncards_text(s['talon'], 's')
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: 1 ea open.

        .. \_start([]) -> None
        """
        self._play_sound('deal')
        self.s['talon'].dealRow()


class TakeAway(game.Game):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 4. Init deal 13 cards. Build none. Play top.

    _foundation_
      Pile 6. Build either in rank alone from any base card wrap. Play none.
    ------
    :cvar Stack _talon_:  stack.InitialDealTalon
    :cvar Stack _foundation_: UD_RK_Foundation (max_move=0, mod=13,
      base_rank=ANY)
    :cvar Stack _row_: stack.BasicRowStack
    """
    __info__ = dict(id=334, name='Take Away', game_type=(GT.ONE_DECK |
                    GT.OPEN), decks=1, redeals=0, skill_level=SL.MOSTLY_SKILL,
                    version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation_ = Wrap(UD_RK_Foundation, max_move=0, mod=13, base_rank=cs.ANY)
    _row_ = stack.BasicRowStack

    def _create_game(self, playcards=16, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes.

        .. \_create_game([playcards=16]) -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (2 * l.XM + 10 * l.XS,
                           l.YM + l._get_stack_height(playcards),)
        for x in l.row_iter(l.XM, 4):
            s['rows'].append(self._row_(x, l.YM, game=self))
        for x in l.row_iter(s['rows'].x + l.XM, 6):
            s['foundations'].append(self._foundation_(x, l.YM, game=self,
                                                   offset=(0, l.YOFFSET)))
        s['talon'] = self._talon_(self.width - l.XS, self.height - l.YS,
                                  game=self)
        l.defaultAll()
        return l

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: 13 ea open.

        .. \_start([]) -> None
        """
        for i in range(10):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        for i in range(3):
            self.s['talon'].dealRow()


class Striptease(game.Game):
    """
    Arrange cards on _row_ with Queens on _reserve_ within a turn.

    _row_
      Pile 4 E _reserve. Build either in rank alone. (J K J)

    _reserve_
      Pile 4. Init deal 13 bottom 1 and top 11 open cards. Build none. Play
      top except base.
    ------
    :cvar Stack _talon_:  stack.InitialDealTalon
    :cvar Stack _row_: Striptease_RowStack (max_move=0, mod=13)
    :cvar Stack _reserve_: Striptease_Reserve (min_cards=1)
    """
    __info__ = dict(id=654, name='Striptease', game_type=GT.ONE_DECK, decks=1,
                    redeals=0, skill_level=SL.MOSTLY_SKILL, version='0.93fc')
    __author__ = 'Skomoroh'
    __credits__ = ['David Parlett']
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _row_ = Wrap(Striptease_RowStack, max_move=0, mod=13)
    _reserve_ = Wrap(Striptease_Reserve, min_cards=1)

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes.

        .. \_create_game(playcards=16) -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + (2 * l.NSUIT + 1) * l.XS,
                           l.YM + l._get_stack_height(kw.get('playcards', 16)),)
        for x in l.row_iter(l.XM, l.NSUIT):
            s['reserves'].append(_pile_(x, y, game=self, offset=(0, l.YOFFSET)))
        dx = x + 2 * l.XS
        for x in l.row_iter(dx, l.NSUIT):
            s['rows'].append(self._row_(x, l.YM, game=self))
        s['talon'] = self._talon_(self.width - l.XS, self.height - l.YS,
                                  game=self)
        l.defaultAll()
        return l

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return QUEEN on top by suit, deck for s[reserves] deal.
        """
        return place.MoveToTop(cards, place.AllQueen)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[reserves]: 13 ea bottom 1 and top 11 open

        .. \_start([]) -> None
        """
        for i in range(10):
            self.s['talon'].dealRow(flip=bool(i != 1), rows=self.s['reserves'],
                                    frames=0)
        self._play_sound('deal')
        for i in range(3):
            self.s['talon'].dealRow(rows=self.s['reserves'])

    @property
    def is_complete(self):
        return all(len(r.cards) == 1 for r in self.s['reserves'])


PROVIDES = [StHelena, BoxKite, LesQuatreCoins, RegalFamily, TakeAway,
            Striptease]